package com.gitee.wsl.compose.chart.xy.line

import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.AnimationSpec
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.layout.absolutePadding
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.drawscope.Fill
import androidx.compose.ui.graphics.drawscope.Stroke
import androidx.compose.ui.graphics.drawscope.clipRect
import androidx.compose.ui.layout.Layout
import androidx.compose.ui.layout.Measurable
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.dp
import com.gitee.wsl.common.ui.draw.style.AreaStyle
import com.gitee.wsl.common.ui.draw.style.LineStyle
import com.gitee.wsl.common.ui.ext.color.random
import com.gitee.wsl.common.ui.ext.path.lineTo
import com.gitee.wsl.common.ui.ext.path.moveTo
import com.gitee.wsl.compose.chart.Chart
import com.gitee.wsl.compose.chart.ChartLayout
import com.gitee.wsl.compose.chart.ExperimentalKoalaPlotApi
import com.gitee.wsl.compose.chart.KoalaPlotTheme
import com.gitee.wsl.compose.chart.SelectCheck
import com.gitee.wsl.compose.chart.legend.Legend
import com.gitee.wsl.compose.chart.legend.LegendLocation
import com.gitee.wsl.compose.chart.legend.LegendSlate
import com.gitee.wsl.compose.chart.legend.defaultLegend
import com.gitee.wsl.compose.chart.xy.SymbolSlate
import com.gitee.wsl.compose.chart.xy.Symbols
import com.gitee.wsl.compose.chart.xy.XYGraph
import com.gitee.wsl.compose.chart.xy.model.PointEntry
import com.gitee.wsl.compose.chart.xy.XYGraphScope
import com.gitee.wsl.compose.chart.xy.axis.AxisLabel
import com.gitee.wsl.compose.chart.xy.axis.AxisModel
import com.gitee.wsl.compose.chart.xy.axis.style.AxisStyle
import com.gitee.wsl.compose.chart.xy.axis.style.rememberAxisStyle
import com.gitee.wsl.compose.chart.xy.line.area.generateArea
import com.gitee.wsl.compose.chart.xy.line.area.model.AreaBaseline
import com.gitee.wsl.compose.modifier.input.gestures.GestureConfig
import com.gitee.wsl.ext.base.ifValue


fun <X, Y> LinePlot.Line(lineStyle: ((Int)->LineStyle?)? = null,
                         symbol: SymbolSlate<PointEntry<X, Y>>? = null,
                         animationSpec: @Composable (Int)->AnimationSpec<Float> = { KoalaPlotTheme.animationSpec },
                         selected : SelectCheck = {false}
) : LinePlotSlate<X, Y> = {index, data->
    LinePlot(
        data = data,
        lineStyle = lineStyle?.invoke(index),
        symbol = symbol,
        animationSpec = animationSpec(index),
        selected = selected(index)
        )
}

/**
 * A line plot that draws data as points and lines on an XYGraph.
 * @param X The type of the x-axis values
 * @param Y The type of the y-axis values
 * @param data Data series to plot.
 * @param lineStyle Style to use for the line that connects the data points. If null, no line is drawn.
 * @param symbol Composable for the symbol to be shown at each data PointEntry.
 * @param modifier Modifier for the plot.
 */
@Composable
 fun <X, Y> XYGraphScope<X, Y>.LinePlot(
    data: List<PointEntry<X, Y>>,
    modifier: Modifier = Modifier,
    lineStyle: LineStyle? = null,
    symbol: SymbolSlate<PointEntry<X, Y>>? = null,
    animationSpec: AnimationSpec<Float> = KoalaPlotTheme.animationSpec,
    selected :Boolean = false
) {
    if (data.isEmpty()) return

    GeneralLinePlot(
        data,
        modifier,
        lineStyle,
        symbol,
        null,
        null,
        animationSpec,
        selected
    ) { points: List<PointEntry<X, Y>>, size: Size ->
        moveTo(scale(points[0], size))
        for (index in 1..points.lastIndex) {
            lineTo(scale(points[index], size))
        }
    }
}

typealias LinePathBuilder<P> = Path.(points: List<P>, size: Size) -> Unit

/**
 * @param X The type of the x-axis values
 * @param Y The type of the y-axis values
 * @param data Data series to plot.
 * @param lineStyle Style to use for the line that connects the data points. If null, no line is drawn.
 * @param symbol Composable for the symbol to be shown at each data PointEntry.
 * @param modifier Modifier for the chart.
 */
@Composable
internal fun <X, Y> XYGraphScope<X, Y>.GeneralLinePlot(
    data: List<PointEntry<X, Y>>,
    modifier: Modifier = Modifier,
    lineStyle: LineStyle? = null,
    symbol: SymbolSlate<PointEntry<X, Y>>? = null,
    areaStyle: AreaStyle? = null,
    areaBaseline: AreaBaseline<X, Y>? = null,
    animationSpec: AnimationSpec<Float>,
    selected :Boolean = false,
    drawConnectorLine: LinePathBuilder<PointEntry<X, Y>>
) {
    if (data.isEmpty()) return

    // Animation scale factor
    val beta = remember { Animatable(0f) }

    LaunchedEffect(null) { beta.animateTo(1f, animationSpec = animationSpec) }

    Layout(
        modifier = modifier.drawWithContent {
            clipRect(right = size.width * beta.value) { (this@drawWithContent).drawContent() }
        },
        content = {
            Canvas(modifier = Modifier.fillMaxSize()) {
                val mainLinePath = Path().apply {
                    drawConnectorLine(data, size)
                }

                if (areaBaseline != null && areaStyle != null) {
                    val areaPath = generateArea(areaBaseline, data, mainLinePath, size, drawConnectorLine)
                    drawPath(
                        areaPath,
                        brush = areaStyle.brush,
                        alpha = areaStyle.alpha,
                        style = Fill,
                        colorFilter = areaStyle.colorFilter,
                        blendMode = areaStyle.blendMode
                    )
                }

                lineStyle?.let {
                    drawPath(
                        mainLinePath,
                        brush = lineStyle.brush,
                        alpha = lineStyle.alpha,
                        style = Stroke(
                            lineStyle.strokeWidth.toPx() * selected.ifValue(1.5f,1f),
                            pathEffect = lineStyle.pathEffect
                        ),
                        colorFilter = lineStyle.colorFilter,
                        blendMode = lineStyle.blendMode
                    )
                }
            }
            Symbols(data, selectCheck = { selected }, symbol)
        }
    ) { measurables: List<Measurable>, constraints: Constraints ->
        layout(constraints.maxWidth, constraints.maxHeight) {
            measurables.forEach {
                it.measure(constraints).place(0, 0)
            }
        }
    }
}